/**
 * Copyright (c) 2010-2011 Pierre Labatut
 *
 * This file is part of Space-Chronicles project.
 *
 * Space-Chronicles is free software: you can redistribute it and/or modify it
 * under the terms of its license which may be found in the accompanying
 * LICENSE file at <http://code.google.com/p/space-chronicles/>.
 */
package space.gl;

import java.io.UnsupportedEncodingException;
import javax.media.opengl.GL2;
import javax.media.opengl.GL3;

/**
 * May be a vertex shader, a fragment shader or a geometry shader. Shader are attached to a program
 * and then linked.
 *
 * @author Pierre
 */
public class Shader {

    final int shaderHandle;

    /**
     * Define macro by addding an header to the file. This implementation is a bypass: the dedicated
     * opengl call that looks bugged at the writing time.
     * @param macroNames and no null array of macro to be defined
     * @return a string made of macro declaration
     */
    private static String defineBlock(final String[] macroNames) {
        if (macroNames == null) {
            throw new IllegalArgumentException();
        }
        final StringBuilder sb = new StringBuilder();
        for (String define : macroNames) {
            sb.append("#define ").append(define).append(";\n");
        }
        return sb.toString();
    }

    public Shader(final GL3 gl, final int shaderType, final String[] macroNames, final String source) {
        if (shaderType != GL3.GL_VERTEX_SHADER
                && shaderType != GL3.GL_FRAGMENT_SHADER
                && shaderType != GL3.GL_GEOMETRY_SHADER) {
            throw new IllegalArgumentException("Unexpected shader type (" + shaderType + ")");
        }

        if (gl == null) {
            throw new IllegalArgumentException();
        }
        if (source == null) {
            throw new IllegalArgumentException();
        }
        //Allocate a new shader program
        shaderHandle = gl.glCreateShader(shaderType);
        //Bind the source to the shader handle
        gl.glShaderSource(shaderHandle, 2, new String[]{defineBlock(macroNames), source}, null, 0);
        //Compile
        gl.glCompileShader(shaderHandle);

        //Check compilation status
        if (glGetShaderi(gl, GL2.GL_COMPILE_STATUS) != GL2.GL_TRUE) {
            throw new RuntimeException(
                    "Shader compilation failed\n" + getInfoLog(gl));
        }
    }

    /**
     * Returns a single integer state of this shader
     * @param gl the current gl context
     * @param pname name of the state to fetch
     * @return single integer shader state
     */
    private int glGetShaderi(final GL3 gl, final int pname) {
        if (gl == null) {
            throw new IllegalArgumentException();
        }
        final int[] result = new int[1];
        gl.glGetShaderiv(shaderHandle, pname, result, 0);
        return result[0];
    }

    /**
     * Returns the log associated with the last compilation of this shader object.
     * @param gl the current gl context
     * @return the complete infolog or null if empty
     */
    private String getInfoLog(GL3 gl) {
        if (gl == null) {
            throw new IllegalArgumentException();
        }

        int infoLength = glGetShaderi(gl, GL2.GL_INFO_LOG_LENGTH);
        if (infoLength <= 0) {
            return null;
        }
        int[] charswritten = new int[1];
        byte[] messageBytes = new byte[infoLength];
        gl.glGetShaderInfoLog(shaderHandle, infoLength, charswritten, 0,
                messageBytes, 0);
        try {
            //Trim is called to remove the terminal null character
            final String message = new String(messageBytes, "US-ASCII").trim();
            return message.isEmpty() ? null : message;
        } catch (UnsupportedEncodingException ex) {
            throw new RuntimeException(ex);
        }
    }
}
